1_
Simplest service in one .svc file
C# <%@ ServiceHost Language="C#" Debug="true" Service="WcfServiceSimpleNs.WcfServiceSimple" %> using System; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace WcfServiceSimpleNs { [ServiceContract] public interface IWcfServiceSimple { [OperationContract] string GetData(int value); } public class WcfServiceSimple : IWcfServiceSimple { public string GetData(int value) { return string.Format("Simplest: Argument passed: {0}", value); } } }
2_
What type of application pool needs to be used
Make sure that you select the AppPool with Integrated Pipeline mode
(When you use classic you wil get 500.21 Error)
3_
Minimum config file
Note: if you do not have any service listed in your config at all, when debugging from Visual Studio with IIS Express
or when hosting in IIS, the hosting server will detect if a service is available and expose it with basicHttpBinding
XML <configuration> <system.serviceModel> <services> <!-- Note: the service name must match the configuration name for the service implementation. --> <service name="WcfServiceSimpleNs.WcfServiceSimple" behaviorConfiguration="MyServiceTypeBehaviors" > <endpoint address="" binding="wsHttpBinding" contract="WcfServiceSimpleNs.IWcfServiceSimple" /> </service> </services> <behaviors> <serviceBehaviors> <behavior name="MyServiceTypeBehaviors" > <serviceMetadata httpGetEnabled="true" /> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>
4_
Disable WSDL
XML <serviceMetadata httpGetEnabled="false" />
5_
From which file reads service its configuration (selfhosted and IIS)
Be aware that the when the service is hosted by IIS then the service reads its System.ServiceModel configuration
from web.config, not from MyService.dll.config,
that gets created by Visual Studio. MyService.dll.config would be used if the service is selfhosted.
6_
What is called ABC
Address - specify location of the service. Client will need to set this to URL of the service.
Binding - defines how client communicates with the service (SOAP, REST, ..)
Contract - defines data transferred (IWcfServiceSimple interface)
Binding - defines how client communicates with the service (SOAP, REST, ..)
Contract - defines data transferred (IWcfServiceSimple interface)
7_
Binding types
basicHttpBinding - SOAP 1.1 (weak security; great on intercompatilibility
wsHttpBinding - SOAP 1.2 (security, reliable messiging, sessionfull connections; heavier
webHttpBinding - REST (does not require a SOAP client to access the messages, XML or JSON retuned based on URL)
netTcpBinding - optimized and faster than wsHttpBinding, secure, intended for WCF-to-WCF communication
All bnding types on MSDN
wsHttpBinding - SOAP 1.2 (security, reliable messiging, sessionfull connections; heavier
webHttpBinding - REST (does not require a SOAP client to access the messages, XML or JSON retuned based on URL)
netTcpBinding - optimized and faster than wsHttpBinding, secure, intended for WCF-to-WCF communication
All bnding types on MSDN
8_
What are the deployment options
1. one embeded file (described above)
2. .svc and .svc.cs file. You can split the .svc file above to WcfSimple.svc
2. .svc and .svc.cs file. You can split the .svc file above to WcfSimple.svc
C# <%@ ServiceHost Language="C#" Debug="true" Service="WcfServiceSimpleNs.WcfServiceSimple" %>and \App_Code\WcfServiceSimple.svc.cs files.
C# using System; ... namespace WcfServiceSimpleNs { public interface IWcfServiceSimple { ... } public class WcfServiceSimple : IWcfServiceSimple { ... } }3. You can compile .svc.cs to a .net dll binary (from Visual Studio or command line) and copy it to \bin folder of the web service. You may need to run c:\Windows\Microsoft.NET\Framework64\v3.0\Windows Communication Foundation\ServiceModelReg.exe -i to install WCF components.
9_
Create self host for WCF service
1. Create console application
2. add reference to System.ServiceModel
2. add reference to System.ServiceModel
C# // Must be executed with VS running As Administrator static void Main(string[] args) { Uri baseSvcAddress = new Uri("http://localhost:9000/MyServiceHost"); // Create the host using (ServiceHost host = new ServiceHost(typeof(WcfServiceSimple), baseSvcAddress)) { // Enable metadata ServiceMetadataBehavior svcMetadataBehavior = new ServiceMetadataBehavior(); svcMetadataBehavior.HttpGetEnabled = true; svcMetadataBehavior.MetadataExporter.PolicyVersion = PolicyVersion.Policy15; host.Description.Behaviors.Add(svcMetadataBehavior); // base service address will be used. host.Open(); Console.WriteLine("The service is running at {0}", baseSvcAddress); Console.WriteLine("Press 'Enter' to stop the service."); Console.ReadLine(); // Close the host. host.Close(); } }
10_
How to create WCF service client
Consuming WCF service:
When developing in Visual Studio In order to be able to create a client with a proxy, you do not need to deploy the service
1. In the same solution as the service project is located, Create a New project (e.g. a Console app)
2. Right click on References and select "Add Service Reference .."
3. Click "Discover/Service in Solution"
When consuming a public service
3. Enter the url of the running wcf service and click Go
4. Visual Studio will generate a Service References folder with a subfolder for your wcf service with about 9 files. Reference.cs file that represents the proxy to the service is created under the subfolder.
When developing in Visual Studio In order to be able to create a client with a proxy, you do not need to deploy the service
1. In the same solution as the service project is located, Create a New project (e.g. a Console app)
2. Right click on References and select "Add Service Reference .."
3. Click "Discover/Service in Solution"
When consuming a public service
3. Enter the url of the running wcf service and click Go
4. Visual Studio will generate a Service References folder with a subfolder for your wcf service with about 9 files. Reference.cs file that represents the proxy to the service is created under the subfolder.
C# // create client using defined configuration from using (var client = new WcfServiceSimpleClient("BasicHttpBinding_IWcfServiceSimple")) { // call a service method with 1 argument string result = client.GetData(2); }
11_
Create client without service reference and app.config
It is possible to create a client and call his methods without specifying parameters in config file
and creating webservice reference. The service interface is needed.
C# // create channel var myBinding = new WebHttpBinding(); var myEndpoint = new EndpointAddress("http://myurl.net"); var channelFactory = new ChannelFactory<IMyInterface>(myBinding, myEndpoint); channelFactory.Endpoint.Behaviors.Add(new WebHttpBehavior()); IComplianceFacade client = null; client = channelFactory.CreateChannel(); // make the call client.MyMethod(); // close channel channelFactory.Close();
12_
How to debug the WCF service from Visual Studio
- In Visual Studio, in Solution Explorer, click on the .svc file.
- Press F5, WCF Test Client will be opened (VS 2013).
- Put a break point into the method that you want to debug
- Select the method in the left panel and click Invoke
Note 1: the service output must be in bin\ folder
Note 2: the <UseIIS> flag in .csproj file must be set to "true" in order the test client starts
Note 3: if you have your own client in the same solution, you can either start both the service and your own client by right clicking on the solution and selecting "Set StartUp Projects..." or just select the client as startup project and VS will start IIS with Service automatically
Note 4: you need to have an endpoint that exposes SOAP based binding (BasicHttpBinding or wsHttpBinding) to see the service in WCF Test Client
Note 5: Visaul Studio starts IIS Express. Make sure that it gets closed before you start the updated service (right on the IIS Express icon in the Task bar, click Exit).
- Press F5, WCF Test Client will be opened (VS 2013).
- Put a break point into the method that you want to debug
- Select the method in the left panel and click Invoke
Note 1: the service output must be in bin\ folder
Note 2: the <UseIIS> flag in .csproj file must be set to "true" in order the test client starts
Note 3: if you have your own client in the same solution, you can either start both the service and your own client by right clicking on the solution and selecting "Set StartUp Projects..." or just select the client as startup project and VS will start IIS with Service automatically
Note 4: you need to have an endpoint that exposes SOAP based binding (BasicHttpBinding or wsHttpBinding) to see the service in WCF Test Client
Note 5: Visaul Studio starts IIS Express. Make sure that it gets closed before you start the updated service (right on the IIS Express icon in the Task bar, click Exit).
13_
What is the purpose of web.config|web.debug.config|web.release.config
The same section in web.config gets replaced with content of web.release.config or web.release.config
(with help of xlst transformation) when the service gets deployed from Visual Studio (2013) using Publish action.
This will not happen during build (F5 in Visual Studio).
Usage: It is useful e.g. for replacing connection string (from local dev DB used during development when starting the service from Visual Studio) to production DB when deploying to production.
web.config
This will not happen during build (F5 in Visual Studio).
Usage: It is useful e.g. for replacing connection string (from local dev DB used during development when starting the service from Visual Studio) to production DB when deploying to production.
web.config
XML <connectionStrings> <add name="MyDB" connectionString="Data Source=.;Initial Catalog=DevelopmentDB;Integrated Security=True" /> </connectionStrings> </code>web.release.config
XML <connectionStrings> <add name="MyDB" connectionString="Data Source=ProductionSQLServer;Initial Catalog=ProductionDB;Integrated Security=True" xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/> </connectionStrings>If the the same action is required to happen during build, there are multiple ways how to run the transformation described here .
14_
Format of transmitted data - simple types
For the method below using simple data types
C# public string MyMethod(int myIntValue, string myStringValue) { return string.Format("Arguments passed: {0} {1}", myIntValue, myStringValue); }here is data send as request:
XML <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header> <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/IWcfServiceSimple/MyMethod</Action> </s:Header> <s:Body> <MyMethod xmlns="http://tempuri.org/"> <myIntValue>2</myIntValue> <myStringValue>abc</myStringValue> </MyMethod> </s:Body> </s:Envelope>and as response:
XML <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header /> <s:Body> <MyMethodResponse xmlns="http://tempuri.org/"> <MyMethodResult>Arguments passed: 2 abc</MyMethodResult> </MyMethodResponse> </s:Body> </s:Envelope>
15_
Format of transmitted data - composite types
For the method below using composite data types
C# [DataContract] public class MyCompositeType { bool boolValue = true; string stringValue = "Hello "; [DataMember] public bool BoolValue { get { return boolValue; } set { boolValue = value; } } [DataMember] public string StringValue { get { return stringValue; } set { stringValue = value; } } } public MyCompositeType GetDataUsingDataContract(MyCompositeType composite) { return composite; }here is data send as request:
XML <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header> <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/IWcfServiceSimple/GetDataUsingDataContract</Action> </s:Header> <s:Body> <GetDataUsingDataContract xmlns="http://tempuri.org/"> <composite xmlns:d4p1="http://schemas.datacontract.org/2004/07/WcfServiceSimple" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <d4p1:BoolValue>false</d4p1:BoolValue> <d4p1:StringValue>abc</d4p1:StringValue> </composite> </GetDataUsingDataContract> </s:Body> </s:Envelope>and as response:
XML <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header /> <s:Body> <GetDataUsingDataContractResponse xmlns="http://tempuri.org/"> <GetDataUsingDataContractResult xmlns:a="http://schemas.datacontract.org/2004/07/WcfServiceSimple" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <a:BoolValue>false</a:BoolValue> <a:StringValue>abc</a:StringValue> </GetDataUsingDataContractResult> </GetDataUsingDataContractResponse> </s:Body> </s:Envelope>
16_
How to enable exception details (2 options)
1. In code
C# [ServiceBehavior(IncludeExceptionDetailInFaults = true)] public class WcfServiceSimple : IWcfServiceSimple2. config file
XML <system.serviceModel> <services> <service name="ServiceNamespace.ServiceClassName" behaviorConfiguration ="DebugEnabled"> </service> </services> <behaviors> <serviceBehaviors > <behavior name="DebugEnabled"> <serviceDebug includeExceptionDetailInFaults="True"/> </behavior> </serviceBehaviors> </behaviors> <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/> </system.serviceModel>
17_
How to configure large WCF messages
XML <system.serviceModel> <bindings> <netTcpBinding> <binding name="largeMessageBinding" receiveTimeout="00:10:00" sendTimeout="00:10:00" maxReceivedMessageSize="1000485760" > <readerQuotas maxArrayLength="1000485760"/> </binding> </netTcpBinding> </bindings> <services> <service name="largeMessageService" <endpoint binding="netTcpBinding" bindingConfiguration="largeMessageBinding" ... /> </service> <services> </system.serviceModel>
18_
What happens if new field is added to composite type or an old field is renamed
If you add a new field as last field to a composite type on the service side and do not update the composite type on the client side, you can still call
the service and the fields of the composite type defined in the proxy used by the client will be populated.
If you change the name of a field on the server side, you can still make a call, but the old name on the client side will not be populated.
The same applies if the composite type is passed as an argument or a return type.
If you change the name of a field on the server side, you can still make a call, but the old name on the client side will not be populated.
The same applies if the composite type is passed as an argument or a return type.
19_
How to inject WCF service with Unity (2.options)
Install Unity Nuget package
(it will add 3 Microsoft.Practices.Unity.*.dlls and Microsoft.Practices.ServiceLocation.dll)
Assuming you have a class that connects to an external repository (e.g. a database) and you want to mock the call for testing.
1. Create Dependency Injection Factory with static constructor and add contructor to EACH dependency class
2. Use Factory attribute in .svc file.
The second case does not require to write special constructors for each dependency class.
(it will add 3 Microsoft.Practices.Unity.*.dlls and Microsoft.Practices.ServiceLocation.dll)
Assuming you have a class that connects to an external repository (e.g. a database) and you want to mock the call for testing.
C# public interface IExternalRepository { int GetData(); } public class ExternalRepository : IExternalRepository { public int GetData() { int result = // read from a database return result; } }and a service depending on the repository
C# public class MyUnityService : IWcfServiceSimple { public string GetData() { return _externalRepository.GetData(); } }There are 2 options how to let unity to inject the classes:
1. Create Dependency Injection Factory with static constructor and add contructor to EACH dependency class
C#
using Microsoft.Practices.Unity;
public class DependencyFactory
{
private static IUnityContainer _container { get; set; }
static DependencyFactory()
{
_container = new UnityContainer();
container.RegisterType<IExternalRepository, ExternalRepository>();
}
public static T Resolve<T>()
{
T result = default(T);
if (_container.IsRegistered(typeof(T)))
{
result = _container.Resolve<T>();
}
return result;
}
}
and add static constructors to the WCF service
C# public class MyUnityService : IWcfServiceSimple { private readonly IExternalRepository _externalRepository; public MyUnityService() : this(DependencyFactory.Resolve<IExternalRepository>()) { } public MyUnityService(IExternalRepository externalRepository) { _externalRepository = externalRepository; } public string GetData() { return _externalRepository.GetData(); } }The example above demonstrates what happens behind the scene in the option #2.
2. Use Factory attribute in .svc file.
XML <%@ ServiceHost Language="C#" Debug="true" Factory="WcfServiceSimpleNs.WcfServiceHostFactory" Service="WcfServiceSimpleNs.WcfServiceSimple2" CodeBehind="WcfServiceSimple2.svc.cs" %>This requires to write a class inherited from ServiceHost
C# public partial class WcfServiceHost : ServiceHost { public WcfServiceHost(IUnityContainer container, Type serviceType, params Uri[] baseAddresses) : base(container.Resolve(serviceType), baseAddresses) { } public WcfServiceHost(IUnityContainer container, Type serviceType) : base(container.Resolve(serviceType)) { } }and the factory inherited from ServiceHostFactory
C# public class WcfServiceHostFactory : ServiceHostFactory { private readonly IUnityContainer _container; public WcfServiceHostFactory() { _container = new UnityContainer(); _container.RegisterType<IExternalRepository, ExternalRepository>(); } protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses) { return new WcfServiceHost(_container, serviceType, baseAddresses); } }The service must be marked with
C# [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] public class WcfServiceSimple2 : IWcfServiceSimple2 { }In both cases, when the WCF service class gets instatiated, the dependency classes (ExternalRepository) will be injected.
The second case does not require to write special constructors for each dependency class.
20_
Turn off authentication
XML <system.serviceModel> <bindings> <wsHttpBinding> <binding name="CustomBinding"> <security mode="None"> <message establishSecurityContext="false"/> <transport clientCredentialType="None"/> </security> </binding> </wsHttpBinding> </bindings> <service> <endpoint ... binding="wsHttpBinding" bindingConfiguration="CustomBinding" > </endpoint> </service> .... <system.serviceModel>
21_
install certificate in local machine storage
The standard installation with double click on the certificate file is not the right installation procedure
if you need a certificate be accessible by a service. The it is necessary:
MSDN Article
As the menu Website | ASP.NET Configuration is not available in Visual studio 2013, you can invoke the configuration web site manually:
Start IIS express (not as admin) - works with .net 4
iisexpress.exe /path:C:\Windows\Microsoft.NET\Framework\v4.0.30319\ASP.NETWebAdminFiles /vpath:"/ASP.NETWebAdminFiles" /port:8082 /clr:4.0 /ntlm
Open ASP configuration (d:\mySolutionFolder is the path where .sln is located)
http://localhost:8082/asp.netwebadminfiles/default.aspx?applicationPhysicalPath=d:\mySolutionFolder\&applicationUrl=/
You will be ask to enter your Windows credentials. If you are logged to the domain, your full user name will look like "domainName\userName"
If you see the error (probably after VS 2015 installed):
Compiler Error Message: CS0122: 'System.Configuration.StringUtil' is inaccessible due to its protection level
Replace Line 989 in C:\Windows\Microsoft.NET\Framework\v4.0.30319\ASP.NETWebAdminFiles\WebAdminPage.cs
string appId = (String.Concat(appPath, appPhysPath).GetHashCode()).ToString("x", CultureInfo.InvariantCulture);
Start mmc Go to File / Add/Remove Snap-in... Double click on Certificates Select Computer account, Next, Finish, OK In Console Window right click on Certificates/Personal/Certificates Select All Tasks/Import Select your certificate file from
ASP.net roles configuration in Visual Studio 2013
Start IIS express (not as admin) - works with .net 4
iisexpress.exe /path:C:\Windows\Microsoft.NET\Framework\v4.0.30319\ASP.NETWebAdminFiles /vpath:"/ASP.NETWebAdminFiles" /port:8082 /clr:4.0 /ntlm
Open ASP configuration (d:\mySolutionFolder is the path where .sln is located)
http://localhost:8082/asp.netwebadminfiles/default.aspx?applicationPhysicalPath=d:\mySolutionFolder\&applicationUrl=/
You will be ask to enter your Windows credentials. If you are logged to the domain, your full user name will look like "domainName\userName"
If you see the error (probably after VS 2015 installed):
Compiler Error Message: CS0122: 'System.Configuration.StringUtil' is inaccessible due to its protection level
Replace Line 989 in C:\Windows\Microsoft.NET\Framework\v4.0.30319\ASP.NETWebAdminFiles\WebAdminPage.cs
string appId = (String.Concat(appPath, appPhysPath).GetHashCode()).ToString("x", CultureInfo.InvariantCulture);
22_
ASP.net configuration in Visual Studio 2013
Start IIS express (not as admin) - works with .net 4
iisexpress.exe /path:C:\Windows\Microsoft.NET\Framework\v4.0.30319\ASP.NETWebAdminFiles /vpath:"/ASP.NETWebAdminFiles" /port:8082 /clr:4.0 /ntlm
Open ASP configuration (d:\mySolutionFolder is the path where .sln is located)
http://localhost:8082/asp.netwebadminfiles/default.aspx?applicationPhysicalPath=d:\mySolutionFolder\&applicationUrl=/
You will be ask to enter your Windows credentials. If you are logged to the domain, your full user name will look like "domainName\userName"
iisexpress.exe /path:C:\Windows\Microsoft.NET\Framework\v4.0.30319\ASP.NETWebAdminFiles /vpath:"/ASP.NETWebAdminFiles" /port:8082 /clr:4.0 /ntlm
Open ASP configuration (d:\mySolutionFolder is the path where .sln is located)
http://localhost:8082/asp.netwebadminfiles/default.aspx?applicationPhysicalPath=d:\mySolutionFolder\&applicationUrl=/
You will be ask to enter your Windows credentials. If you are logged to the domain, your full user name will look like "domainName\userName"
23_
Assign certificate to client in code
C#
using System.Security.Cryptography.X509Certificates;
var store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificatesFound = store.Certificates.Find(X509FindType.FindBySubjectName, (object)"MyCertificateName", true);
store.Close();
channelFactory.Credentials.ClientCertificate.Certificate = certificatesFound[0];
24_
Add http header to request
Adding a header to request requires to implement IClientMessageInspector and IEndpointBehavior
C# public class HttpHeaderEndpointBehavior : IEndpointBehavior { private readonly IDictionary<string, string> _headers; public HttpHeaderEndpointBehavior(IDictionary<string, string> headers) { _headers = headers; } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { var inspector = new HttpHeaderClientMessageInspector(_headers); clientRuntime.MessageInspectors.Add(inspector); } // implementation of abstract nmethods public void Validate(ServiceEndpoint endpoint) { } public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } } public class HttpHeaderClientMessageInspector : IClientMessageInspector { private readonly IDictionary<string, string> _headers; public HttpHeaderClientMessageInspector(IDictionary<string, string> headers) { _headers = headers; } public object BeforeSendRequest(ref Message request, IClientChannel channel) { HttpRequestMessageProperty httpRequestMessage; object httpRequestMessageObject; if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out httpRequestMessageObject)) { httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty; if (httpRequestMessage != null) { foreach (var h in _headers) { httpRequestMessage.Headers[h.Key] = h.Value; } } } return null; } public void AfterReceiveReply(ref Message reply, object correlationState) { } } // add header to channel factory before creating the channel // HttpHeaderEndpointBehavior will be called before each request var headerDict = new Dictionary<string, string>(); headerDict.Add("MyHeaderKey", "MyHeaderValue"); facadeChannelFactory.Endpoint.Behaviors.Add(new HttpHeaderEndpointBehavior(headerDict));